<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8" />
  <title>小智声波配网</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
  <style>
    body {
      font-family: "Segoe UI", "PingFang SC", sans-serif;
      background: #f0f2f5;
      margin: 0;
      padding: 2rem 1rem;
      display: flex;
      justify-content: center;
    }
    .card {
      background: #fff;
      padding: 2rem 1.5rem;
      border-radius: 16px;
      box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
      max-width: 400px;
      width: 100%;
    }
    h2 {
      text-align: center;
      margin-bottom: 2rem;
    }
    label {
      font-weight: bold;
      display: block;
      margin: 1rem 0 0.3rem;
    }
    input[type="text"],
    input[type="password"] {
      width: 100%;
      padding: 0.75rem;
      font-size: 1rem;
      border-radius: 8px;
      border: 1px solid #ccc;
      box-sizing: border-box;
    }
    input[type="checkbox"] {
      margin-right: 0.5rem;
    }
    .checkbox-container {
      margin-top: 1rem;
      font-size: 0.95rem;
    }
    button {
      width: 100%;
      margin-top: 1rem;
      padding: 0.8rem;
      font-size: 1rem;
      border: none;
      border-radius: 8px;
      background-color: #4a90e2;
      color: #fff;
      cursor: pointer;
      transition: background-color 0.2s;
    }
    button:hover {
      background-color: #357ab8;
    }
    button:active {
      background-color: #2f6ea2;
    }
    audio {
      margin-top: 1.5rem;
      width: 100%;
      outline: none;
    }
  </style>
</head>
<body>
  <div class="card">
    <h2>📶 小智声波配网</h2>
    <label for="ssid">WiFi 名称</label>
    <input id="ssid" type="text" value="" placeholder="请输入 WiFi 名称" />

    <label for="pwd">WiFi 密码</label>
    <input id="pwd" type="password" value="" placeholder="请输入 WiFi 密码" />

    <div class="checkbox-container">
      <label><input type="checkbox" id="loopCheck" checked /> 自动循环播放声波</label>
    </div>

    <button onclick="generate()">🎵 生成并播放声波</button>
    <button onclick="stopPlay()">⏹️ 停止播放</button>
    <audio id="player" controls></audio>
  </div>

  <script>
    const MARK = 1800;
    const SPACE = 1500;
    const SAMPLE_RATE = 44100;
    const BIT_RATE = 100;
    const START_BYTES = [0x01, 0x02];
    const END_BYTES = [0x03, 0x04];
    let loopTimer = null;

    function checksum(data) {
      return data.reduce((sum, b) => (sum + b) & 0xff, 0);
    }

    function toBits(byte) {
      const bits = [];
      for (let i = 7; i >= 0; i--) bits.push((byte >> i) & 1);
      return bits;
    }

    function afskModulate(bits) {
      const samplesPerBit = SAMPLE_RATE / BIT_RATE;
      const totalSamples = Math.floor(bits.length * samplesPerBit);
      const buffer = new Float32Array(totalSamples);
      for (let i = 0; i < bits.length; i++) {
        const freq = bits[i] ? MARK : SPACE;
        for (let j = 0; j < samplesPerBit; j++) {
          const t = (i * samplesPerBit + j) / SAMPLE_RATE;
          buffer[i * samplesPerBit + j] = Math.sin(2 * Math.PI * freq * t);
        }
      }
      return buffer;
    }

    function floatTo16BitPCM(floatSamples) {
      const buffer = new Uint8Array(floatSamples.length * 2);
      for (let i = 0; i < floatSamples.length; i++) {
        const s = Math.max(-1, Math.min(1, floatSamples[i]));
        const val = s < 0 ? s * 0x8000 : s * 0x7fff;
        buffer[i * 2] = val & 0xff;
        buffer[i * 2 + 1] = (val >> 8) & 0xff;
      }
      return buffer;
    }

    function buildWav(pcm) {
      const wavHeader = new Uint8Array(44);
      const dataLen = pcm.length;
      const fileLen = 36 + dataLen;

      const writeStr = (offset, str) => {
        for (let i = 0; i < str.length; i++) wavHeader[offset + i] = str.charCodeAt(i);
      };
      const write32 = (offset, value) => {
        wavHeader[offset] = value & 0xff;
        wavHeader[offset + 1] = (value >> 8) & 0xff;
        wavHeader[offset + 2] = (value >> 16) & 0xff;
        wavHeader[offset + 3] = (value >> 24) & 0xff;
      };
      const write16 = (offset, value) => {
        wavHeader[offset] = value & 0xff;
        wavHeader[offset + 1] = (value >> 8) & 0xff;
      };

      writeStr(0, 'RIFF');
      write32(4, fileLen);
      writeStr(8, 'WAVE');
      writeStr(12, 'fmt ');
      write32(16, 16);
      write16(20, 1);
      write16(22, 1);
      write32(24, SAMPLE_RATE);
      write32(28, SAMPLE_RATE * 2);
      write16(32, 2);
      write16(34, 16);
      writeStr(36, 'data');
      write32(40, dataLen);

      return new Blob([wavHeader, pcm], { type: 'audio/wav' });
    }

    function generate() {
      stopPlay();
      const ssid = document.getElementById('ssid').value.trim();
      const pwd = document.getElementById('pwd').value.trim();
      const dataStr = ssid + '\n' + pwd;
      const textBytes = Array.from(new TextEncoder().encode(dataStr));
      const fullBytes = [...START_BYTES, ...textBytes, checksum(textBytes), ...END_BYTES];

      let bits = [];
      fullBytes.forEach((b) => (bits = bits.concat(toBits(b))));

      const floatBuf = afskModulate(bits);
      const pcmBuf = floatTo16BitPCM(floatBuf);
      const wavBlob = buildWav(pcmBuf);

      const audio = document.getElementById('player');
      audio.src = URL.createObjectURL(wavBlob);
      audio.load();
      audio.play();

      // 修改了这里：使用 'ended' 事件来实现循环播放
      if (document.getElementById('loopCheck').checked) {
        audio.onended = function() {
          audio.currentTime = 0;  // 从头开始
          audio.play();           // 重新播放
        };
      }
    }

    function stopPlay() {
      const audio = document.getElementById('player');
      audio.pause();
      audio.onended = null;  // 清除事件监听
    }
  </script>
</body>
</html>
